﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using SiteParser.Core.Logging;
using SiteParser.Core.Formatters;

namespace SiteParser.Core.ParserEntities
{
	public abstract class ParserEntity
	{
		private IList<ParserEntity> childEntities;
		private HtmlNavigationExpression expression;
		private string propertyName;
		private string formatterName;
		private IFormatter formatter;

		protected IFormatter Formatter {
			get {
				if (formatter == null) {
					formatter = FormattersMapper.GetFormatter(formatterName);
				}

				return formatter;
			}
		}

		public string FormatterName {
			get { return formatterName; }
			set { formatterName = value; }
		}

		public string PropertyName {
			get { return propertyName; }
			set { propertyName = value; }
		}

		public HtmlNavigationExpression Expression {
			get { return expression; }
			set { expression = value; }
		}

		public IList<ParserEntity> ChildEntities {
			get {
				if (childEntities == null) {
					childEntities = new List<ParserEntity>();
				}
				return childEntities;
			}
		}


		public void Parse(ParsingContext context) {
			if (context.InnerHtml != null) {
				try {
					using (var contextReader = new StringReader(context.InnerHtml)) {
						var navigator = new HtmlNavigator(contextReader);
						var htmlIterator = navigator.Select(expression);
						context.CurrentExpression = expression;

						if (expression.TagMatchingRule != null) {
							for (int i = 0; i < expression.TagMatchingRule.MatchIndex && htmlIterator.MoveNext(); i++) { }
						}

						ProcessCurrentContext(context, htmlIterator);
					}
				}
				catch (Exception exc) {
					Logger.Error(string.Format("Error occured during executing the following parser '{0}'. {1}", this.GetType(), exc));
				}
			}
		}

		protected virtual void ProcessCurrentContext(ParsingContext context, IHtmlIterator htmlIterator) {
			throw new NotImplementedException("ProcessCurrentContext");
		}

		protected virtual void ProcessChildEntities(ParsingContext context) {
			if (ChildEntities != null) {
				foreach (var child in ChildEntities) {
					if (child != null) {
						child.Parse(context);
					}
				}
			}
		}

		protected virtual void SetPropertyValue(object entity, string propertyName, object propertyValue, bool throwIfNullParameters) {
			if (entity == null && throwIfNullParameters) {
				throw new ArgumentNullException("entity");
			}
			if (string.IsNullOrEmpty(propertyName) && throwIfNullParameters) {
				throw new ArgumentNullException("propertyName");
			}

			Type entityType = entity.GetType();
			var property = entityType.GetProperty(propertyName);

			if (property == null) {
				throw new NotSupportedException("Specified property is not supported.");
			}
			if (!property.CanWrite)
			{
				throw new NotSupportedException(string.Format("The property '{0}' doesn't support set method", propertyName));
			}
			if (propertyValue != null && (!property.PropertyType.IsSubclassOf(propertyValue.GetType()) && !property.PropertyType.Equals(propertyValue.GetType()))) {
				throw new InvalidCastException(string.Format("Cannot implicit convert type {0} to {1}", propertyValue.GetType(), property.PropertyType));
			}

			property.SetValue(entity, propertyValue, null);
		}

		protected virtual object InitializeProperty(object entity, string propertyName) {
			var propertyInfo = GetObjectPropertyInfo(entity, propertyName, true);

			if (propertyInfo == null) {
				throw new NotSupportedException("Specified property is not supported.");
			}
			if (!propertyInfo.CanWrite) {
				throw new NotSupportedException(string.Format("The property '{0}' doesn't support set method", propertyName));
			}

			if (propertyInfo.GetValue(entity, null) == null) {
				ConstructorInfo propertyConstructor = propertyInfo.PropertyType.GetConstructor(new Type[0]);
				propertyInfo.SetValue(entity, propertyConstructor != null ? propertyConstructor.Invoke(new object[0]) : null, null);
			}

			return propertyInfo.GetValue(entity, null);
		}

		private PropertyInfo GetObjectPropertyInfo(object entity, string propertyName, bool throwIfNullParameters) {
			if (entity == null && throwIfNullParameters) {
				throw new ArgumentNullException("entity");
			}
			if (string.IsNullOrEmpty(propertyName) && throwIfNullParameters) {
				throw new ArgumentNullException("propertyName");
			}

			Type entityType = entity.GetType();
			return entityType.GetProperty(propertyName);
		}
	}
}
